<?php

namespace CuiFox\yii\validators;

use yii\validators\Validator;

class CreditCodeValidator extends Validator
{
    /**
     * @param \yii\base\Model $model
     * @param string $attribute
     */
    public function validateAttribute($model, $attribute)
    {
        if (!$this->validateCreditCode($model->$attribute)) {
            $this->addError($model, $attribute, '社会统一信用代码无效');
        }
    }

    /**
     * @param $creditCode
     * @return bool
     */
    private function validateCreditCode($creditCode)
    {
        $pattern = '/^[0-9A-Z]{18}$/';
        if (preg_match($pattern, $creditCode)) {
            // 验证注册地行政区划码
            $regionCodes = [
                '11', '12', '13', '14', '15', '21', '22', '23', '31', '32', '33', '34', '35', '36',
                '37', '41', '42', '43', '44', '45', '46', '50', '51', '52', '53', '54', '61', '62',
                '63', '64', '65', '71', '81', '82', '91',
            ];
            $regionCode = substr($creditCode, 0, 2);
            if (!in_array($regionCode, $regionCodes)) {
                return false;
            }

            // 计算校验位
            $weights = [1, 3, 9, 27, 19, 26, 16, 17, 20, 29, 25, 13, 8, 24, 10, 30, 28];
            $characters = str_split($creditCode);
            $checksum = 0;
            for ($i = 0; $i < 17; $i++) {
                $char = $characters[$i];
                $value = strpos('0123456789ABCDEFGHJKLMNPQRTUWXY', $char);
                $weight = $weights[$i];
                $checksum += $value * $weight;
            }
            $mod = $checksum % 31;
            $checkCode = '0123456789ABCDEFGHJKLMNPQRTUWXY'[$mod];
            if ($checkCode !== $characters[17]) {
                return false;
            }

            // 额外验证 第17/18位必须是其中一个
            $s = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'];
            if (!in_array($characters[16], $s) || !in_array($characters[17], $s)) {
                return false;
            }

            $nineToSeventeen = substr($creditCode, 8, 9);
            if (preg_match('/^[0]{9}$/', $nineToSeventeen)) {
                return false;
            }
            return true;
        }
        return false;
    }
}